skill-seekers 2.7.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- skill_seekers/__init__.py +22 -0
- skill_seekers/cli/__init__.py +39 -0
- skill_seekers/cli/adaptors/__init__.py +120 -0
- skill_seekers/cli/adaptors/base.py +221 -0
- skill_seekers/cli/adaptors/claude.py +485 -0
- skill_seekers/cli/adaptors/gemini.py +453 -0
- skill_seekers/cli/adaptors/markdown.py +269 -0
- skill_seekers/cli/adaptors/openai.py +503 -0
- skill_seekers/cli/ai_enhancer.py +310 -0
- skill_seekers/cli/api_reference_builder.py +373 -0
- skill_seekers/cli/architectural_pattern_detector.py +525 -0
- skill_seekers/cli/code_analyzer.py +1462 -0
- skill_seekers/cli/codebase_scraper.py +1225 -0
- skill_seekers/cli/config_command.py +563 -0
- skill_seekers/cli/config_enhancer.py +431 -0
- skill_seekers/cli/config_extractor.py +871 -0
- skill_seekers/cli/config_manager.py +452 -0
- skill_seekers/cli/config_validator.py +394 -0
- skill_seekers/cli/conflict_detector.py +528 -0
- skill_seekers/cli/constants.py +72 -0
- skill_seekers/cli/dependency_analyzer.py +757 -0
- skill_seekers/cli/doc_scraper.py +2332 -0
- skill_seekers/cli/enhance_skill.py +488 -0
- skill_seekers/cli/enhance_skill_local.py +1096 -0
- skill_seekers/cli/enhance_status.py +194 -0
- skill_seekers/cli/estimate_pages.py +433 -0
- skill_seekers/cli/generate_router.py +1209 -0
- skill_seekers/cli/github_fetcher.py +534 -0
- skill_seekers/cli/github_scraper.py +1466 -0
- skill_seekers/cli/guide_enhancer.py +723 -0
- skill_seekers/cli/how_to_guide_builder.py +1267 -0
- skill_seekers/cli/install_agent.py +461 -0
- skill_seekers/cli/install_skill.py +178 -0
- skill_seekers/cli/language_detector.py +614 -0
- skill_seekers/cli/llms_txt_detector.py +60 -0
- skill_seekers/cli/llms_txt_downloader.py +104 -0
- skill_seekers/cli/llms_txt_parser.py +150 -0
- skill_seekers/cli/main.py +558 -0
- skill_seekers/cli/markdown_cleaner.py +132 -0
- skill_seekers/cli/merge_sources.py +806 -0
- skill_seekers/cli/package_multi.py +77 -0
- skill_seekers/cli/package_skill.py +241 -0
- skill_seekers/cli/pattern_recognizer.py +1825 -0
- skill_seekers/cli/pdf_extractor_poc.py +1166 -0
- skill_seekers/cli/pdf_scraper.py +617 -0
- skill_seekers/cli/quality_checker.py +519 -0
- skill_seekers/cli/rate_limit_handler.py +438 -0
- skill_seekers/cli/resume_command.py +160 -0
- skill_seekers/cli/run_tests.py +230 -0
- skill_seekers/cli/setup_wizard.py +93 -0
- skill_seekers/cli/split_config.py +390 -0
- skill_seekers/cli/swift_patterns.py +560 -0
- skill_seekers/cli/test_example_extractor.py +1081 -0
- skill_seekers/cli/test_unified_simple.py +179 -0
- skill_seekers/cli/unified_codebase_analyzer.py +572 -0
- skill_seekers/cli/unified_scraper.py +932 -0
- skill_seekers/cli/unified_skill_builder.py +1605 -0
- skill_seekers/cli/upload_skill.py +162 -0
- skill_seekers/cli/utils.py +432 -0
- skill_seekers/mcp/__init__.py +33 -0
- skill_seekers/mcp/agent_detector.py +316 -0
- skill_seekers/mcp/git_repo.py +273 -0
- skill_seekers/mcp/server.py +231 -0
- skill_seekers/mcp/server_fastmcp.py +1249 -0
- skill_seekers/mcp/server_legacy.py +2302 -0
- skill_seekers/mcp/source_manager.py +285 -0
- skill_seekers/mcp/tools/__init__.py +115 -0
- skill_seekers/mcp/tools/config_tools.py +251 -0
- skill_seekers/mcp/tools/packaging_tools.py +826 -0
- skill_seekers/mcp/tools/scraping_tools.py +842 -0
- skill_seekers/mcp/tools/source_tools.py +828 -0
- skill_seekers/mcp/tools/splitting_tools.py +212 -0
- skill_seekers/py.typed +0 -0
- skill_seekers-2.7.3.dist-info/METADATA +2027 -0
- skill_seekers-2.7.3.dist-info/RECORD +79 -0
- skill_seekers-2.7.3.dist-info/WHEEL +5 -0
- skill_seekers-2.7.3.dist-info/entry_points.txt +19 -0
- skill_seekers-2.7.3.dist-info/licenses/LICENSE +21 -0
- skill_seekers-2.7.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skill Seekers - Convert documentation, GitHub repos, and PDFs into Claude AI skills.
|
|
3
|
+
|
|
4
|
+
This package provides tools for automatically scraping, organizing, and packaging
|
|
5
|
+
documentation from various sources into uploadable Claude AI skills.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "2.7.2"
|
|
9
|
+
__author__ = "Yusuf Karaaslan"
|
|
10
|
+
__license__ = "MIT"
|
|
11
|
+
|
|
12
|
+
# Expose main components for easier imports
|
|
13
|
+
from skill_seekers.cli import __version__ as cli_version
|
|
14
|
+
from skill_seekers.mcp import __version__ as mcp_version
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"__version__",
|
|
18
|
+
"__author__",
|
|
19
|
+
"__license__",
|
|
20
|
+
"cli_version",
|
|
21
|
+
"mcp_version",
|
|
22
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Skill Seekers CLI tools package.
|
|
2
|
+
|
|
3
|
+
This package provides command-line tools for converting documentation
|
|
4
|
+
websites into Claude AI skills.
|
|
5
|
+
|
|
6
|
+
Main modules:
|
|
7
|
+
- doc_scraper: Main documentation scraping and skill building tool
|
|
8
|
+
- llms_txt_detector: Detect llms.txt files at documentation URLs
|
|
9
|
+
- llms_txt_downloader: Download llms.txt content
|
|
10
|
+
- llms_txt_parser: Parse llms.txt markdown content
|
|
11
|
+
- pdf_scraper: Extract documentation from PDF files
|
|
12
|
+
- enhance_skill: AI-powered skill enhancement (API-based)
|
|
13
|
+
- enhance_skill_local: AI-powered skill enhancement (local)
|
|
14
|
+
- estimate_pages: Estimate page count before scraping
|
|
15
|
+
- package_skill: Package skills into .zip files
|
|
16
|
+
- upload_skill: Upload skills to Claude
|
|
17
|
+
- utils: Shared utility functions
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .llms_txt_detector import LlmsTxtDetector
|
|
21
|
+
from .llms_txt_downloader import LlmsTxtDownloader
|
|
22
|
+
from .llms_txt_parser import LlmsTxtParser
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from .utils import open_folder, read_reference_files
|
|
26
|
+
except ImportError:
|
|
27
|
+
# utils.py might not exist in all configurations
|
|
28
|
+
open_folder = None
|
|
29
|
+
read_reference_files = None
|
|
30
|
+
|
|
31
|
+
__version__ = "2.7.2"
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"LlmsTxtDetector",
|
|
35
|
+
"LlmsTxtDownloader",
|
|
36
|
+
"LlmsTxtParser",
|
|
37
|
+
"open_folder",
|
|
38
|
+
"read_reference_files",
|
|
39
|
+
]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Multi-LLM Adaptor Registry
|
|
4
|
+
|
|
5
|
+
Provides factory function to get platform-specific adaptors for skill generation.
|
|
6
|
+
Supports Claude AI, Google Gemini, OpenAI ChatGPT, and generic Markdown export.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .base import SkillAdaptor, SkillMetadata
|
|
10
|
+
|
|
11
|
+
# Import adaptors (some may not be implemented yet)
|
|
12
|
+
try:
|
|
13
|
+
from .claude import ClaudeAdaptor
|
|
14
|
+
except ImportError:
|
|
15
|
+
ClaudeAdaptor = None
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from .gemini import GeminiAdaptor
|
|
19
|
+
except ImportError:
|
|
20
|
+
GeminiAdaptor = None
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from .openai import OpenAIAdaptor
|
|
24
|
+
except ImportError:
|
|
25
|
+
OpenAIAdaptor = None
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
from .markdown import MarkdownAdaptor
|
|
29
|
+
except ImportError:
|
|
30
|
+
MarkdownAdaptor = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Registry of available adaptors
|
|
34
|
+
ADAPTORS: dict[str, type[SkillAdaptor]] = {}
|
|
35
|
+
|
|
36
|
+
# Register adaptors that are implemented
|
|
37
|
+
if ClaudeAdaptor:
|
|
38
|
+
ADAPTORS["claude"] = ClaudeAdaptor
|
|
39
|
+
if GeminiAdaptor:
|
|
40
|
+
ADAPTORS["gemini"] = GeminiAdaptor
|
|
41
|
+
if OpenAIAdaptor:
|
|
42
|
+
ADAPTORS["openai"] = OpenAIAdaptor
|
|
43
|
+
if MarkdownAdaptor:
|
|
44
|
+
ADAPTORS["markdown"] = MarkdownAdaptor
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_adaptor(platform: str, config: dict = None) -> SkillAdaptor:
|
|
48
|
+
"""
|
|
49
|
+
Factory function to get platform-specific adaptor instance.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
platform: Platform identifier ('claude', 'gemini', 'openai', 'markdown')
|
|
53
|
+
config: Optional platform-specific configuration
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
SkillAdaptor instance for the specified platform
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ValueError: If platform is not supported or not yet implemented
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
>>> adaptor = get_adaptor('claude')
|
|
63
|
+
>>> adaptor = get_adaptor('gemini', {'api_version': 'v1beta'})
|
|
64
|
+
"""
|
|
65
|
+
if platform not in ADAPTORS:
|
|
66
|
+
available = ", ".join(ADAPTORS.keys())
|
|
67
|
+
if not ADAPTORS:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"No adaptors are currently implemented. Platform '{platform}' is not available."
|
|
70
|
+
)
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"Platform '{platform}' is not supported or not yet implemented. Available platforms: {available}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
adaptor_class = ADAPTORS[platform]
|
|
76
|
+
return adaptor_class(config)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def list_platforms() -> list[str]:
|
|
80
|
+
"""
|
|
81
|
+
List all supported platforms.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of platform identifiers
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
>>> list_platforms()
|
|
88
|
+
['claude', 'gemini', 'openai', 'markdown']
|
|
89
|
+
"""
|
|
90
|
+
return list(ADAPTORS.keys())
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def is_platform_available(platform: str) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
Check if a platform adaptor is available.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
platform: Platform identifier to check
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
True if platform is available
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
>>> is_platform_available('claude')
|
|
105
|
+
True
|
|
106
|
+
>>> is_platform_available('unknown')
|
|
107
|
+
False
|
|
108
|
+
"""
|
|
109
|
+
return platform in ADAPTORS
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Export public interface
|
|
113
|
+
__all__ = [
|
|
114
|
+
"SkillAdaptor",
|
|
115
|
+
"SkillMetadata",
|
|
116
|
+
"get_adaptor",
|
|
117
|
+
"list_platforms",
|
|
118
|
+
"is_platform_available",
|
|
119
|
+
"ADAPTORS",
|
|
120
|
+
]
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Base Adaptor for Multi-LLM Support
|
|
4
|
+
|
|
5
|
+
Defines the abstract interface that all platform-specific adaptors must implement.
|
|
6
|
+
This enables Skill Seekers to generate skills for multiple LLM platforms (Claude, Gemini, ChatGPT).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SkillMetadata:
|
|
17
|
+
"""Universal skill metadata used across all platforms"""
|
|
18
|
+
|
|
19
|
+
name: str
|
|
20
|
+
description: str
|
|
21
|
+
version: str = "1.0.0"
|
|
22
|
+
author: str | None = None
|
|
23
|
+
tags: list[str] = field(default_factory=list)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SkillAdaptor(ABC):
|
|
27
|
+
"""
|
|
28
|
+
Abstract base class for platform-specific skill adaptors.
|
|
29
|
+
|
|
30
|
+
Each platform (Claude, Gemini, OpenAI) implements this interface to handle:
|
|
31
|
+
- Platform-specific SKILL.md formatting
|
|
32
|
+
- Platform-specific package structure (ZIP, tar.gz, etc.)
|
|
33
|
+
- Platform-specific upload endpoints and authentication
|
|
34
|
+
- Optional AI enhancement capabilities
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Platform identifiers (override in subclasses)
|
|
38
|
+
PLATFORM: str = "unknown" # e.g., "claude", "gemini", "openai"
|
|
39
|
+
PLATFORM_NAME: str = "Unknown" # e.g., "Claude AI (Anthropic)"
|
|
40
|
+
DEFAULT_API_ENDPOINT: str | None = None
|
|
41
|
+
|
|
42
|
+
def __init__(self, config: dict[str, Any] | None = None):
|
|
43
|
+
"""
|
|
44
|
+
Initialize adaptor with optional configuration.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
config: Platform-specific configuration options
|
|
48
|
+
"""
|
|
49
|
+
self.config = config or {}
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def format_skill_md(self, skill_dir: Path, metadata: SkillMetadata) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Format SKILL.md content with platform-specific frontmatter/structure.
|
|
55
|
+
|
|
56
|
+
Different platforms require different formats:
|
|
57
|
+
- Claude: YAML frontmatter + markdown
|
|
58
|
+
- Gemini: Plain markdown (no frontmatter)
|
|
59
|
+
- OpenAI: Assistant instructions format
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
skill_dir: Path to skill directory containing references/
|
|
63
|
+
metadata: Skill metadata (name, description, version, etc.)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Formatted SKILL.md content as string
|
|
67
|
+
"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def package(self, skill_dir: Path, output_path: Path) -> Path:
|
|
72
|
+
"""
|
|
73
|
+
Package skill for platform (ZIP, tar.gz, etc.).
|
|
74
|
+
|
|
75
|
+
Different platforms require different package formats:
|
|
76
|
+
- Claude: .zip with SKILL.md, references/, scripts/, assets/
|
|
77
|
+
- Gemini: .tar.gz with system_instructions.md, references/
|
|
78
|
+
- OpenAI: .zip with assistant_instructions.txt, vector_store_files/
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
skill_dir: Path to skill directory to package
|
|
82
|
+
output_path: Path for output package (file or directory)
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Path to created package file
|
|
86
|
+
"""
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def upload(self, package_path: Path, api_key: str, **kwargs) -> dict[str, Any]:
|
|
91
|
+
"""
|
|
92
|
+
Upload packaged skill to platform.
|
|
93
|
+
|
|
94
|
+
Returns a standardized response dictionary for all platforms.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
package_path: Path to packaged skill file
|
|
98
|
+
api_key: Platform API key
|
|
99
|
+
**kwargs: Additional platform-specific arguments
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dictionary with keys:
|
|
103
|
+
- success (bool): Whether upload succeeded
|
|
104
|
+
- skill_id (str|None): Platform-specific skill/assistant ID
|
|
105
|
+
- url (str|None): URL to view/manage skill
|
|
106
|
+
- message (str): Success or error message
|
|
107
|
+
"""
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
def validate_api_key(self, api_key: str) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Validate API key format for this platform.
|
|
113
|
+
|
|
114
|
+
Default implementation just checks if key is non-empty.
|
|
115
|
+
Override for platform-specific validation.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
api_key: API key to validate
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if key format is valid
|
|
122
|
+
"""
|
|
123
|
+
return bool(api_key and api_key.strip())
|
|
124
|
+
|
|
125
|
+
def get_env_var_name(self) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Get expected environment variable name for API key.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Environment variable name (e.g., "ANTHROPIC_API_KEY", "GOOGLE_API_KEY")
|
|
131
|
+
"""
|
|
132
|
+
return f"{self.PLATFORM.upper()}_API_KEY"
|
|
133
|
+
|
|
134
|
+
def supports_enhancement(self) -> bool:
|
|
135
|
+
"""
|
|
136
|
+
Whether this platform supports AI-powered SKILL.md enhancement.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if platform can enhance skills
|
|
140
|
+
"""
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
def enhance(self, _skill_dir: Path, _api_key: str) -> bool:
|
|
144
|
+
"""
|
|
145
|
+
Optionally enhance SKILL.md using platform's AI.
|
|
146
|
+
|
|
147
|
+
Only called if supports_enhancement() returns True.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
skill_dir: Path to skill directory
|
|
151
|
+
api_key: Platform API key
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
True if enhancement succeeded
|
|
155
|
+
"""
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
def _read_existing_content(self, skill_dir: Path) -> str:
|
|
159
|
+
"""
|
|
160
|
+
Helper to read existing SKILL.md content (without frontmatter).
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
skill_dir: Path to skill directory
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
SKILL.md content without YAML frontmatter
|
|
167
|
+
"""
|
|
168
|
+
skill_md_path = skill_dir / "SKILL.md"
|
|
169
|
+
if not skill_md_path.exists():
|
|
170
|
+
return ""
|
|
171
|
+
|
|
172
|
+
content = skill_md_path.read_text(encoding="utf-8")
|
|
173
|
+
|
|
174
|
+
# Strip YAML frontmatter if present
|
|
175
|
+
if content.startswith("---"):
|
|
176
|
+
parts = content.split("---", 2)
|
|
177
|
+
if len(parts) >= 3:
|
|
178
|
+
return parts[2].strip()
|
|
179
|
+
|
|
180
|
+
return content
|
|
181
|
+
|
|
182
|
+
def _extract_quick_reference(self, skill_dir: Path) -> str:
|
|
183
|
+
"""
|
|
184
|
+
Helper to extract quick reference section from references.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
skill_dir: Path to skill directory
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Quick reference content as markdown string
|
|
191
|
+
"""
|
|
192
|
+
index_path = skill_dir / "references" / "index.md"
|
|
193
|
+
if not index_path.exists():
|
|
194
|
+
return "See references/ directory for documentation."
|
|
195
|
+
|
|
196
|
+
# Read index and extract relevant sections
|
|
197
|
+
content = index_path.read_text(encoding="utf-8")
|
|
198
|
+
return content[:500] + "..." if len(content) > 500 else content
|
|
199
|
+
|
|
200
|
+
def _generate_toc(self, skill_dir: Path) -> str:
|
|
201
|
+
"""
|
|
202
|
+
Helper to generate table of contents from references.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
skill_dir: Path to skill directory
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Table of contents as markdown string
|
|
209
|
+
"""
|
|
210
|
+
refs_dir = skill_dir / "references"
|
|
211
|
+
if not refs_dir.exists():
|
|
212
|
+
return ""
|
|
213
|
+
|
|
214
|
+
toc_lines = []
|
|
215
|
+
for ref_file in sorted(refs_dir.glob("*.md")):
|
|
216
|
+
if ref_file.name == "index.md":
|
|
217
|
+
continue
|
|
218
|
+
title = ref_file.stem.replace("_", " ").title()
|
|
219
|
+
toc_lines.append(f"- [{title}](references/{ref_file.name})")
|
|
220
|
+
|
|
221
|
+
return "\n".join(toc_lines)
|